Skip to content

fix(profile): refuse a working directory inside the plugin - #129

Merged
mmaclay merged 1 commit into
mainfrom
fix/59-followups
Nov 9, 2026
Merged

mmaclay merged 1 commit into
mainfrom
fix/59-followups

Conversation

@mmaclay

@mmaclay mmaclay commented Nov 9, 2026

Copy link
Copy Markdown
Member

Every skill and hook learns a repository's settings, such as which tracker it uses, from scripts/profile.py, run from the repository's own directory. When a model ran it from inside the plugin instead, it silently read the plugin's settings: in a sandbox run, Haiku cd-ed into the plugin and ran profile.py --defaults --field issue_tracker.kind, the command ticket-audit gives, got not yet recorded, and took the Jira path on a GitHub Issues repository. Any skill that branches on the profile could do the same, with no error to show for it. Once this merges, profile.py never answers from the plugin's own directory as if it were the repository.

It now exits 1 in every mode when the working directory is the plugin's top level or inside it, with the working directory is inside the plugin (<root>), not a repository being worked on. --defaults is included, because it too reads the working directory's standard. One case is exempt: a working directory whose git checkout is the plugin's top level or sits under it, as when a dev-tools checkout is loaded in place (the devcontainer's local marketplace, --plugin-dir) or a worktree under it is worked on. There the plugin is the repository, and refusing would blank the status line and every profile read while working on dev-tools. The installed plugin is not a git checkout, so it always refuses. Paths are compared by file identity, so a path typed in another letter case on macOS is still caught. docs/contracts.md's profile section is at version 17.

Limit: the sandbox's plugin is a git clone, so the exemption also covers it, and the run that showed the bug would still go uncaught there. The refusal is proven by tests/test_profile.py instead: a non-git plugin copy refuses all three modes, including from inside an outer checkout, and a plugin checkout and a worktree under it read github from their own standard.

Of #59's other follow-ups, save_page.py's one-text-block result stays as merged in #124, and #127 is left for its own branch: the attempted fix showed no sandbox difference on Haiku.

Closes #126

Origin: #126
Sandbox: tracker-gh-audit-* on Haiku, 2 runs each. On current main (006cf11), 1 of 8 runs went into the plugin and read the wrong tracker. On this branch, no run went into the plugin, so the refusal was not exercised; it is checked by the unit tests.
Canary: none yet

🤖 Generated with Claude Code. AI assistance level: 5/5 (not read by a person).

- profile.py exits 1 in every mode, --defaults included, when the
  working directory is the plugin's top level or inside it, compared
  by file identity; a git checkout at or under that top level, such
  as a dev-tools checkout loaded in place or a worktree under it, is
  read as the repository instead
- the check runs after argparse's usage errors
- test_profile.py's in-process helper runs from a temporary
  directory; new tests cover the refusal from a non-git plugin copy
  and one inside an outer checkout, and the read from a plugin
  checkout and its worktree
- contracts.md's profile section, version 17, gives the new exit

Closes #126

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mmaclay mmaclay self-assigned this Nov 9, 2026
@mmaclay
mmaclay marked this pull request as ready for review November 9, 2026 20:56
@mmaclay
mmaclay merged commit bef3560 into main Nov 9, 2026
1 check passed
@mmaclay
mmaclay deleted the fix/59-followups branch November 9, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ticket-audit: works from the plugin's own profile when run from its root

1 participant